
<?php

class Tree
{
    private $originalData = [];
    private $nodeNameKey = 'name';
    private $fatherNodeKey = 'pid';
    private $sonNodeKey = 'code';
    private $levelKey = 'level';
    private $topLevelId = 0;

    private $sonFatherHash = [];
    private $nodeNameHash = [];
    private $allNodeIdList = [];
    private $fatherIdHash = [];
    private $fatherIdList = [];
    private $sonIdList = [];
    private $fatherTreePath = [];
    private $sonTreePath = [];
    private $allNodeTreePath = [];

    public function __construct(array $data = [], array $option = [])
    {
        $this->originalData = $data;
        $this->setNodeNameKey($option['node_name_key'] ?? '');
        $this->setFatherNodeKey($option['father_name_key'] ?? '');
        $this->setSonNodeKey($option['son_node_key'] ?? '');
        $this->setLevelKey($option['level_key'] ?? '');
        $this->setTopLevelId($option['top_level_id'] ?? 0);
        $this->init();
    }

    public function getAllNodeTreePath()
    {
        $this->mergeAllTreePath();
        asort($this->allNodeTreePath);
        $list = [];

        foreach ($this->allNodeTreePath as $key => $value) {
            $list[]  = [
                $this->sonNodeKey => (string)$key,
                $this->nodeNameKey => $this->nodeNameHash[$key],
                $this->fatherNodeKey => $this->sonFatherHash[$key],
                $this->levelKey => substr_count($value, '-'),
            ];
        }
        return $list;
    }

    private function setNodeNameKey(string $node_name_key = '')
    {
        $node_name_key === '' || $this->nodeNameKey = $node_name_key;
    }

    private function setFatherNodeKey(string $father_node_key = '')
    {
        $father_node_key === '' || $this->fatherNodeKey = $father_node_key;
    }

    private function setSonNodeKey(string $son_node_key = '')
    {
        $son_node_key === '' || $this->sonNodeKey = $son_node_key;
    }

    private function setLevelKey(string $level_key = '')
    {
        $level_key === '' || $this->levelKey = $level_key;
    }

    private function setTopLevelId(string $top_level_id = '')
    {
        $top_level_id === '' || $this->topLevelId = $top_level_id;
    }

    private function init()
    {
        $this->initSonFatherHash();        
        $this->initNodeNameHash();        
        $this->initAllNodeIdList();        
        $this->initFatherIdHsh();        
        $this->initFatherIdList();
        $this->initSonIdList();
        $this->initFatherTreePath();
        $this->initSonTreePath();
    }

    private function initSonFatherHash()
    {
        $this->sonFatherHash = array_column($this->originalData, $this->fatherNodeKey, $this->sonNodeKey);
    }

    private function initNodeNameHash()
    {
        $this->nodeNameHash = array_column($this->originalData, $this->nodeNameKey, $this->sonNodeKey);
    }

    private function initAllNodeIdList()
    {
        $this->allNodeIdList = array_keys($this->sonFatherHash);
    }

    private function initFatherIdHsh()
    {
        $this->fatherIdHash = array_flip($this->sonFatherHash);
        unset($this->fatherIdHash[$this->topLevelId]);
    }

    private function initFatherIdList()
    {
        $this->fatherIdList = array_keys($this->fatherIdHash);
    }

    private function initSonIdList()
    {
        foreach ($this->allNodeIdList as $v) {
            isset($this->fatherIdList[$v]) || $this->sonIdList[] = $v;
        }
    }

    private function initFatherTreePath()
    {
        // get father tree path
        $father_tree = [];
        foreach ($this->fatherIdList as $value) {
            $father_tree[$value] = $item_id = $value;
            while ($item_id != $this->topLevelId) {
                $item_id = $this->getFatherId($item_id, $father_tree);
                // has handle father node tree , need merge it
                if (isset($father_tree[$item_id])) {
                    $father_tree[$value] = $father_tree[$item_id] . '-' . $father_tree[$value];
                    break;
                }
                // father node tree not handle, need add it
                $father_tree[$value] = $item_id . '-' . $father_tree[$value];
            }
        }
        $this->fatherTreePath = $father_tree;
    }

    private function initSonTreePath()
    {
        // get son tree path
        $son_tree = [];
        foreach ($this->sonIdList as $son_id) {
            $father_path = $this->fatherTreePath[$this->sonFatherHash[$son_id]] ?? [];
            $son_tree[$son_id] = $father_path ? ($father_path . '-' . $son_id) : '';
        }
        $this->sonTreePath = $son_tree;
    }

    private function mergeAllTreePath()
    {
        $this->allNodeTreePath = $this->fatherTreePath + $this->sonTreePath;
    }

    private function getFatherId(string $son_id = '')
    {
        return $this->sonFatherHash[$son_id];
    }

}
